package it.unicam.cs.cosy.bioshape.core.behaviours.abstraction;

import it.unicam.cs.cosy.bioshape.core.behaviours.Behaviour;
import it.unicam.cs.cosy.bioshape.core.components.Pair;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * Generates all the possible traces given the specific set of abstract
 * processes.
 * 
 * @author Federico Buti
 */
public class TraceGenerator {

	private LinkedList<AProcess> processes;
	private HashSet<AProcess> traces;
	private int index;
	private TupleComparator tc = new TupleComparator();

	// TODO serve un input più complesso
	TraceGenerator(ArrayList<Behaviour> behaviours) {
		processes = new LinkedList<AProcess>();
		traces = new HashSet<AProcess>();

		for (Behaviour b : behaviours) {
			processes.add(new AProcess());
		}
		// TODO: skip delays on INITIAL addition!!
		// TODO: add the initial processes to the set of traces!
		// TODO: order the initial processes!
	}

	/**
	 * Builds the generator of abstract traces. It simply instantiates the
	 * support data structures containing the generated processes and the
	 * processes still to check.
	 */
	public TraceGenerator() {
		processes = new LinkedList<AProcess>();
		traces = new HashSet<AProcess>();

	}

	/**
	 * Adds a simple process to the initial set of processes.
	 * 
	 * @param name
	 *            {@code String} representing the name of the concrete shape.
	 * 
	 * @param b
	 *            The {@code Behaviour} of the concrete process.
	 */
	public void addConcreteProcess(String name, Behaviour b) {
		// TODO: skip delays on INITIAL addition!!
		// TODO: add the initial processes to the set of traces!
		// TODO: order the initial processes!
		ABProcess ba = new ABProcess(name, b);
		AProcess a = new AProcess(ba);
		// a.skipDelays();
		Collections.sort(a.bonds, tc);
		traces.add((AProcess) a.clone());
		processes.add(a);
	}

	/**
	 * Adds a
	 * 
	 * @param b
	 *            the behaviour
	 */
	public void addConcreteProcess(Behaviour b) {
		// TODO: skip delays on INITIAL addition!!
		// TODO: add the initial processes to the set of traces!
		// TODO: order the initial processes!
		ABProcess ba = new ABProcess("S_" + index++, b);
		AProcess a = new AProcess(ba);
		// a.skipDelays();
		Collections.sort(a.bonds, tc);
		traces.add((AProcess) a.clone());
		processes.add(a);
	}

	/**
	 * Executes the fixed-point iteration defined in "Towards Abstraction-Based
	 * Verification of Shape Calculus". The generated abstract processes are
	 * stored for debugging purposes in a data structure of the class.
	 */
	public void generateTraces() {
		ArrayList<AProcess> results = new ArrayList<AProcess>();
		while (!processes.isEmpty()) {
			AProcess p = processes.removeFirst();
			p.skipDelays();
			// BINDINGS
			for (AProcess pTemp : traces) {
				// results.clear();
				p.bind(pTemp, results);
			}
			checkAddition(results);
			// WEAK SPLITS
			p.removeWeakSplitBonds(results);
			checkAddition(results);
			// STRONG SPLITS
			p.removeStrongSplitBonds(results);
			checkAddition(results);
		}
		for (AProcess a : traces) {
			System.out.println(a.toString());
		}
	}

	/**
	 * Checks if the processes returned by {{@link #generateTraces()} can be
	 * added to the set of other traces. It it is the case, the processes are
	 * also added to the set of processes to check for the next execution.<br>
	 * While checking each process, it is also removed from the data structure
	 * so that it is empty for the next generation of traces (either due to
	 * bindings, weak- and strong-splittings).
	 * 
	 * @param results
	 *            The data structure loaded with {@code AProcess} processes
	 *            generated by {@link #generateTraces()}.
	 */
	void checkAddition(ArrayList<AProcess> results) {
		for (Iterator<AProcess> it = results.iterator(); it.hasNext();) {
			AProcess temp = it.next();
			Collections.sort(temp.bonds, tc);
			if (traces.add(temp)) {
				processes.add(temp);
			}
			it.remove();
		}
	}

	/**
	 * Compares two {@code AdjTuples} for what concerns the label,
	 * {@code ABProcess#getLabel()}, of the {@code ABProcess} objects. If it is
	 * the same the processes are compared on the basis of their
	 * {@code AdJTuple} objects.<br>
	 * Each tuple is compared to the corresponding for what concerns name of the
	 * bond and label of the process.
	 * 
	 * @author Federico Buti
	 */
	class TupleComparator implements
			Comparator<Pair<ABProcess, ArrayList<AdjTuple>>> {

		@Override
		public int compare(Pair<ABProcess, ArrayList<AdjTuple>> a,
				Pair<ABProcess, ArrayList<AdjTuple>> b) {
			int value = a.getE1().getLabel().compareTo(b.getE1().getLabel());
			if (value != 0)
				return value;
			ArrayList<AdjTuple> listA = a.getE2();
			ArrayList<AdjTuple> listB = b.getE2();
			if (listA.size() < listB.size())
				return -1;
			if (listA.size() > listB.size())
				return 1;
			for (int i = 0; i < listA.size(); i++) {
				value = listA.get(i).compareTo(listB.get(i));
				if (value != 0)
					return value;
			}
			return 0;
		}
	}
}